/*
 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package com.sun.jmx.snmp.daemon;

// java imports
//

import java.util.Vector;
import java.util.Enumeration;
import java.util.logging.Level;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;

// jmx imports
//
import javax.management.MBeanServer;
import javax.management.MBeanRegistration;
import javax.management.ObjectName;

import static com.sun.jmx.defaults.JmxProperties.SNMP_ADAPTOR_LOGGER;

import com.sun.jmx.snmp.SnmpIpAddress;
import com.sun.jmx.snmp.SnmpMessage;
import com.sun.jmx.snmp.SnmpOid;
import com.sun.jmx.snmp.SnmpPduFactory;
import com.sun.jmx.snmp.SnmpPduPacket;
import com.sun.jmx.snmp.SnmpPduRequest;
import com.sun.jmx.snmp.SnmpPduTrap;
import com.sun.jmx.snmp.SnmpTimeticks;
import com.sun.jmx.snmp.SnmpVarBind;
import com.sun.jmx.snmp.SnmpVarBindList;
import com.sun.jmx.snmp.SnmpDefinitions;
import com.sun.jmx.snmp.SnmpStatusException;
import com.sun.jmx.snmp.SnmpTooBigException;
import com.sun.jmx.snmp.InetAddressAcl;
import com.sun.jmx.snmp.SnmpPeer;
import com.sun.jmx.snmp.SnmpParameters;
// SNMP Runtime imports
//
import com.sun.jmx.snmp.SnmpPduFactoryBER;
import com.sun.jmx.snmp.agent.SnmpMibAgent;
import com.sun.jmx.snmp.agent.SnmpMibHandler;
import com.sun.jmx.snmp.agent.SnmpUserDataFactory;
import com.sun.jmx.snmp.agent.SnmpErrorHandlerAgent;

import com.sun.jmx.snmp.IPAcl.SnmpAcl;

import com.sun.jmx.snmp.tasks.ThreadService;

/**
 * Implements an adaptor on top of the SNMP protocol.
 * <P>
 * When this SNMP protocol adaptor is started it creates a datagram socket
 * and is able to receive requests and send traps or inform requests.
 * When it is stopped, the socket is closed and neither requests
 * and nor traps/inform request are processed.
 * <P>
 * The default port number of the socket is 161. This default value can be
 * changed by specifying a port number:
 * <UL>
 * <LI>in the object constructor</LI>
 * <LI>using the {@link com.sun.jmx.snmp.daemon.CommunicatorServer#setPort
 * setPort} method before starting the adaptor</LI>
 * </UL>
 * The default object name is defined by {@link
 * com.sun.jmx.snmp.ServiceName#DOMAIN com.sun.jmx.snmp.ServiceName.DOMAIN}
 * and {@link com.sun.jmx.snmp.ServiceName#SNMP_ADAPTOR_SERVER
 * com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_SERVER}.
 * <P>
 * The SNMP protocol adaptor supports versions 1 and 2 of the SNMP protocol
 * in a stateless way: when it receives a v1 request, it replies with a v1
 * response, when it receives a v2 request it replies with a v2 response.
 * <BR>The method {@link #snmpV1Trap snmpV1Trap} sends traps using SNMP v1
 * format.
 * The method {@link #snmpV2Trap snmpV2Trap} sends traps using SNMP v2 format.
 * The method {@link #snmpInformRequest snmpInformRequest} sends inform
 * requests using SNMP v2 format.
 * <P>
 * To receive data packets, the SNMP protocol adaptor uses a buffer
 * which size can be configured using the property <CODE>bufferSize</CODE>
 * (default value is 1024).
 * Packets which do not fit into the buffer are rejected.
 * Increasing <CODE>bufferSize</CODE> allows the exchange of bigger packets.
 * However, the underlying networking system may impose a limit on the size
 * of UDP packets.
 * Packets which size exceed this limit will be rejected, no matter what
 * the value of <CODE>bufferSize</CODE> actually is.
 * <P>
 * An SNMP protocol adaptor may serve several managers concurrently. The
 * number of concurrent managers can be limited using the property
 * <CODE>maxActiveClientCount</CODE>.
 * <p>
 * The SNMP protocol adaptor specifies a default value (10) for the
 * <CODE>maxActiveClientCount</CODE> property. When the adaptor is stopped,
 * the active requests are interrupted and an error result is sent to
 * the managers.
 * <p><b>This API is a Sun Microsystems internal API  and is subject
 * to change without notice.</b></p>
 */

public class SnmpAdaptorServer extends CommunicatorServer
    implements SnmpAdaptorServerMBean, MBeanRegistration, SnmpDefinitions,
    SnmpMibHandler {

  // PRIVATE VARIABLES
  //------------------

  /**
   * Port number for sending SNMP traps.
   * <BR>The default value is 162.
   */
  private int trapPort = 162;

  /**
   * Port number for sending SNMP inform requests.
   * <BR>The default value is 162.
   */
  private int informPort = 162;

  /**
   * The <CODE>InetAddress</CODE> used when creating the datagram socket.
   * <BR>It is specified when creating the SNMP protocol adaptor.
   * If not specified, the local host machine is used.
   */
  InetAddress address = null;

  /**
   * The IP address based ACL used by this SNMP protocol adaptor.
   */
  private InetAddressAcl ipacl = null;

  /**
   * The factory object.
   */
  private SnmpPduFactory pduFactory = null;

  /**
   * The user-data factory object.
   */
  private SnmpUserDataFactory userDataFactory = null;

  /**
   * Indicates if the SNMP protocol adaptor sends a response in case
   * of authentication failure
   */
  private boolean authRespEnabled = true;

  /**
   * Indicates if authentication traps are enabled.
   */
  private boolean authTrapEnabled = true;

  /**
   * The enterprise OID.
   * <BR>The default value is "1.3.6.1.4.1.42".
   */
  private SnmpOid enterpriseOid = new SnmpOid("1.3.6.1.4.1.42");

  /**
   * The buffer size of the SNMP protocol adaptor.
   * This buffer size is used for both incoming request and outgoing
   * inform requests.
   * <BR>The default value is 1024.
   */
  int bufferSize = 1024;

  private transient long startUpTime = 0;
  private transient DatagramSocket socket = null;
  transient DatagramSocket trapSocket = null;
  private transient SnmpSession informSession = null;
  private transient DatagramPacket packet = null;
  transient Vector<SnmpMibAgent> mibs = new Vector<>();
  private transient SnmpMibTree root;

  /**
   * Whether ACL must be used.
   */
  private transient boolean useAcl = true;

  // SENDING SNMP INFORMS STUFF
  //---------------------------

  /**
   * Number of times to try an inform request before giving up.
   * The default number is 3.
   */
  private int maxTries = 3;

  /**
   * The amount of time to wait for an inform response from the manager.
   * The default amount of time is 3000 millisec.
   */
  private int timeout = 3 * 1000;

  // VARIABLES REQUIRED FOR IMPLEMENTING SNMP GROUP (MIBII)
  //-------------------------------------------------------

  /**
   * The <CODE>snmpOutTraps</CODE> value defined in MIB-II.
   */
  int snmpOutTraps = 0;

  /**
   * The <CODE>snmpOutGetResponses</CODE> value defined in MIB-II.
   */
  private int snmpOutGetResponses = 0;

  /**
   * The <CODE>snmpOutGenErrs</CODE> value defined in MIB-II.
   */
  private int snmpOutGenErrs = 0;

  /**
   * The <CODE>snmpOutBadValues</CODE> value defined in MIB-II.
   */
  private int snmpOutBadValues = 0;

  /**
   * The <CODE>snmpOutNoSuchNames</CODE> value defined in MIB-II.
   */
  private int snmpOutNoSuchNames = 0;

  /**
   * The <CODE>snmpOutTooBigs</CODE> value defined in MIB-II.
   */
  private int snmpOutTooBigs = 0;

  /**
   * The <CODE>snmpOutPkts</CODE> value defined in MIB-II.
   */
  int snmpOutPkts = 0;

  /**
   * The <CODE>snmpInASNParseErrs</CODE> value defined in MIB-II.
   */
  private int snmpInASNParseErrs = 0;

  /**
   * The <CODE>snmpInBadCommunityUses</CODE> value defined in MIB-II.
   */
  private int snmpInBadCommunityUses = 0;

  /**
   * The <CODE>snmpInBadCommunityNames</CODE> value defined in MIB-II.
   */
  private int snmpInBadCommunityNames = 0;

  /**
   * The <CODE>snmpInBadVersions</CODE> value defined in MIB-II.
   */
  private int snmpInBadVersions = 0;

  /**
   * The <CODE>snmpInGetRequests</CODE> value defined in MIB-II.
   */
  private int snmpInGetRequests = 0;

  /**
   * The <CODE>snmpInGetNexts</CODE> value defined in MIB-II.
   */
  private int snmpInGetNexts = 0;

  /**
   * The <CODE>snmpInSetRequests</CODE> value defined in MIB-II.
   */
  private int snmpInSetRequests = 0;

  /**
   * The <CODE>snmpInPkts</CODE> value defined in MIB-II.
   */
  private int snmpInPkts = 0;

  /**
   * The <CODE>snmpInTotalReqVars</CODE> value defined in MIB-II.
   */
  private int snmpInTotalReqVars = 0;

  /**
   * The <CODE>snmpInTotalSetVars</CODE> value defined in MIB-II.
   */
  private int snmpInTotalSetVars = 0;

  /**
   * The <CODE>snmpInTotalSetVars</CODE> value defined in rfc 1907 MIB-II.
   */
  private int snmpSilentDrops = 0;

  private static final String InterruptSysCallMsg =
      "Interrupted system call";
  static final SnmpOid sysUpTimeOid = new SnmpOid("1.3.6.1.2.1.1.3.0");
  static final SnmpOid snmpTrapOidOid = new SnmpOid("1.3.6.1.6.3.1.1.4.1.0");

  private ThreadService threadService;

  private static int threadNumber = 6;

  static {
    String s = System.getProperty("com.sun.jmx.snmp.threadnumber");

    if (s != null) {
      try {
        threadNumber = Integer.parseInt(System.getProperty(s));
      } catch (Exception e) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINER,
            SnmpAdaptorServer.class.getName(),
            "<static init>",
            "Got wrong value for com.sun.jmx.snmp.threadnumber: " +
                s + ". Use the default value: " + threadNumber);
      }
    }
  }

  // PUBLIC CONSTRUCTORS
  //--------------------

  /**
   * Initializes this SNMP protocol adaptor using the default port (161).
   * Use the {@link com.sun.jmx.snmp.IPAcl.SnmpAcl} default
   * implementation of the <CODE>InetAddressAcl</CODE> interface.
   */
  public SnmpAdaptorServer() {
    this(true, null, com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_PORT,
        null);
  }

  /**
   * Initializes this SNMP protocol adaptor using the specified port.
   * Use the {@link com.sun.jmx.snmp.IPAcl.SnmpAcl} default
   * implementation of the <CODE>InetAddressAcl</CODE> interface.
   *
   * @param port The port number for sending SNMP responses.
   */
  public SnmpAdaptorServer(int port) {
    this(true, null, port, null);
  }

  /**
   * Initializes this SNMP protocol adaptor using the default port (161)
   * and the specified IP address based ACL implementation.
   *
   * @param acl The <CODE>InetAddressAcl</CODE> implementation. <code>null</code> means no ACL -
   * everybody is authorized.
   * @since 1.5
   */
  public SnmpAdaptorServer(InetAddressAcl acl) {
    this(false, acl, com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_PORT,
        null);
  }

  /**
   * Initializes this SNMP protocol adaptor using the default port (161)
   * and the
   * specified <CODE>InetAddress</CODE>.
   * Use the {@link com.sun.jmx.snmp.IPAcl.SnmpAcl} default
   * implementation of the <CODE>InetAddressAcl</CODE> interface.
   *
   * @param addr The IP address to bind.
   */
  public SnmpAdaptorServer(InetAddress addr) {
    this(true, null, com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_PORT,
        addr);
  }

  /**
   * Initializes this SNMP protocol adaptor using the specified port and the
   * specified IP address based ACL implementation.
   *
   * @param acl The <CODE>InetAddressAcl</CODE> implementation. <code>null</code> means no ACL -
   * everybody is authorized.
   * @param port The port number for sending SNMP responses.
   * @since 1.5
   */
  public SnmpAdaptorServer(InetAddressAcl acl, int port) {
    this(false, acl, port, null);
  }

  /**
   * Initializes this SNMP protocol adaptor using the specified port and the
   * specified <CODE>InetAddress</CODE>.
   * Use the {@link com.sun.jmx.snmp.IPAcl.SnmpAcl} default
   * implementation of the <CODE>InetAddressAcl</CODE> interface.
   *
   * @param port The port number for sending SNMP responses.
   * @param addr The IP address to bind.
   */
  public SnmpAdaptorServer(int port, InetAddress addr) {
    this(true, null, port, addr);
  }

  /**
   * Initializes this SNMP protocol adaptor using the specified IP
   * address based ACL implementation and the specified
   * <CODE>InetAddress</CODE>.
   *
   * @param acl The <CODE>InetAddressAcl</CODE> implementation.
   * @param addr The IP address to bind.
   * @since 1.5
   */
  public SnmpAdaptorServer(InetAddressAcl acl, InetAddress addr) {
    this(false, acl, com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_PORT,
        addr);
  }

  /**
   * Initializes this SNMP protocol adaptor using the specified port, the
   * specified  address based ACL implementation and the specified
   * <CODE>InetAddress</CODE>.
   *
   * @param acl The <CODE>InetAddressAcl</CODE> implementation.
   * @param port The port number for sending SNMP responses.
   * @param addr The IP address to bind.
   * @since 1.5
   */
  public SnmpAdaptorServer(InetAddressAcl acl, int port, InetAddress addr) {
    this(false, acl, port, addr);
  }

  /**
   * Initializes this SNMP protocol adaptor using the specified port and the
   * specified <CODE>InetAddress</CODE>.
   * This constructor allows to initialize an SNMP adaptor without using
   * the ACL mechanism (by setting the <CODE>useAcl</CODE> parameter to
   * false).
   * <br>This constructor must be used in particular with a platform that
   * does not support the <CODE>java.security.acl</CODE> package like pJava.
   *
   * @param useAcl Specifies if this new SNMP adaptor uses the ACL mechanism. If the specified
   * parameter is set to <CODE>true</CODE>, this constructor is equivalent to
   * <CODE>SnmpAdaptorServer((int)port,(InetAddress)addr)</CODE>.
   * @param port The port number for sending SNMP responses.
   * @param addr The IP address to bind.
   */
  public SnmpAdaptorServer(boolean useAcl, int port, InetAddress addr) {
    this(useAcl, null, port, addr);
  }

  // If forceAcl is `true' and InetAddressAcl is null, then a default
  // SnmpAcl object is created.
  //
  private SnmpAdaptorServer(boolean forceAcl, InetAddressAcl acl,
      int port, InetAddress addr) {
    super(CommunicatorServer.SNMP_TYPE);

    // Initialize the ACL implementation.
    //
    if (acl == null && forceAcl) {
      try {
        acl = new SnmpAcl("SNMP protocol adaptor IP ACL");
      } catch (UnknownHostException e) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "constructor", "UnknowHostException when creating ACL", e);
        }
      }
    } else {
      this.useAcl = (acl != null) || forceAcl;
    }

    init(acl, port, addr);
  }

  // GETTERS AND SETTERS
  //--------------------

  /**
   * Gets the number of managers that have been processed by this
   * SNMP protocol adaptor  since its creation.
   *
   * @return The number of managers handled by this SNMP protocol adaptor since its creation. This
   * counter is not reset by the <CODE>stop</CODE> method.
   */
  @Override
  public int getServedClientCount() {
    return super.getServedClientCount();
  }

  /**
   * Gets the number of managers currently being processed by this
   * SNMP protocol adaptor.
   *
   * @return The number of managers currently being processed by this SNMP protocol adaptor.
   */
  @Override
  public int getActiveClientCount() {
    return super.getActiveClientCount();
  }

  /**
   * Gets the maximum number of managers that this SNMP protocol adaptor can
   * process concurrently.
   *
   * @return The maximum number of managers that this SNMP protocol adaptor can process
   * concurrently.
   */
  @Override
  public int getMaxActiveClientCount() {
    return super.getMaxActiveClientCount();
  }

  /**
   * Sets the maximum number of managers this SNMP protocol adaptor can
   * process concurrently.
   *
   * @param c The number of managers.
   * @throws java.lang.IllegalStateException This method has been invoked while the communicator was
   * <CODE>ONLINE</CODE> or <CODE>STARTING</CODE>.
   */
  @Override
  public void setMaxActiveClientCount(int c)
      throws java.lang.IllegalStateException {
    super.setMaxActiveClientCount(c);
  }

  /**
   * Returns the Ip address based ACL used by this SNMP protocol adaptor.
   *
   * @return The <CODE>InetAddressAcl</CODE> implementation.
   * @since 1.5
   */
  @Override
  public InetAddressAcl getInetAddressAcl() {
    return ipacl;
  }

  /**
   * Returns the port used by this SNMP protocol adaptor for sending traps.
   * By default, port 162 is used.
   *
   * @return The port number for sending SNMP traps.
   */
  @Override
  public Integer getTrapPort() {
    return new Integer(trapPort);
  }

  /**
   * Sets the port used by this SNMP protocol adaptor for sending traps.
   *
   * @param port The port number for sending SNMP traps.
   */
  @Override
  public void setTrapPort(Integer port) {
    setTrapPort(port.intValue());
  }

  /**
   * Sets the port used by this SNMP protocol adaptor for sending traps.
   *
   * @param port The port number for sending SNMP traps.
   */
  public void setTrapPort(int port) {
    int val = port;
    if (val < 0) {
      throw new
          IllegalArgumentException("Trap port cannot be a negative value");
    }
    trapPort = val;
  }

  /**
   * Returns the port used by this SNMP protocol adaptor for sending
   * inform requests. By default, port 162 is used.
   *
   * @return The port number for sending SNMP inform requests.
   */
  @Override
  public int getInformPort() {
    return informPort;
  }

  /**
   * Sets the port used by this SNMP protocol adaptor for sending
   * inform requests.
   *
   * @param port The port number for sending SNMP inform requests.
   */
  @Override
  public void setInformPort(int port) {
    if (port < 0) {
      throw new IllegalArgumentException("Inform request port " +
          "cannot be a negative value");
    }
    informPort = port;
  }

  /**
   * Returns the protocol of this SNMP protocol adaptor.
   *
   * @return The string "snmp".
   */
  @Override
  public String getProtocol() {
    return "snmp";
  }

  /**
   * Returns the buffer size of this SNMP protocol adaptor.
   * This buffer size is used for both incoming request and outgoing
   * inform requests.
   * By default, buffer size 1024 is used.
   *
   * @return The buffer size.
   */
  @Override
  public Integer getBufferSize() {
    return new Integer(bufferSize);
  }

  /**
   * Sets the buffer size of this SNMP protocol adaptor.
   * This buffer size is used for both incoming request and outgoing
   * inform requests.
   *
   * @param s The buffer size.
   * @throws java.lang.IllegalStateException This method has been invoked while the communicator was
   * <CODE>ONLINE</CODE> or <CODE>STARTING</CODE>.
   */
  @Override
  public void setBufferSize(Integer s)
      throws java.lang.IllegalStateException {
    if ((state == ONLINE) || (state == STARTING)) {
      throw new IllegalStateException("Stop server before carrying out" +
          " this operation");
    }
    bufferSize = s.intValue();
  }

  /**
   * Gets the number of times to try sending an inform request before
   * giving up.
   * By default, a maximum of 3 tries is used.
   *
   * @return The maximun number of tries.
   */
  @Override
  final public int getMaxTries() {
    return maxTries;
  }

  /**
   * Changes the maximun number of times to try sending an inform
   * request before giving up.
   *
   * @param newMaxTries The maximun number of tries.
   */
  @Override
  final public synchronized void setMaxTries(int newMaxTries) {
    if (newMaxTries < 0) {
      throw new IllegalArgumentException();
    }
    maxTries = newMaxTries;
  }

  /**
   * Gets the timeout to wait for an inform response from the manager.
   * By default, a timeout of 3 seconds is used.
   *
   * @return The value of the timeout property.
   */
  @Override
  final public int getTimeout() {
    return timeout;
  }

  /**
   * Changes the timeout to wait for an inform response from the manager.
   *
   * @param newTimeout The timeout (in milliseconds).
   */
  @Override
  final public synchronized void setTimeout(int newTimeout) {
    if (newTimeout < 0) {
      throw new IllegalArgumentException();
    }
    timeout = newTimeout;
  }

  /**
   * Returns the message factory of this SNMP protocol adaptor.
   *
   * @return The factory object.
   */
  @Override
  public SnmpPduFactory getPduFactory() {
    return pduFactory;
  }

  /**
   * Sets the message factory of this SNMP protocol adaptor.
   *
   * @param factory The factory object (null means the default factory).
   */
  @Override
  public void setPduFactory(SnmpPduFactory factory) {
    if (factory == null) {
      pduFactory = new SnmpPduFactoryBER();
    } else {
      pduFactory = factory;
    }
  }

  /**
   * Set the user-data factory of this SNMP protocol adaptor.
   *
   * @param factory The factory object (null means no factory).
   * @see com.sun.jmx.snmp.agent.SnmpUserDataFactory
   */
  @Override
  public void setUserDataFactory(SnmpUserDataFactory factory) {
    userDataFactory = factory;
  }

  /**
   * Get the user-data factory associated with this SNMP protocol adaptor.
   *
   * @return The factory object (null means no factory).
   * @see com.sun.jmx.snmp.agent.SnmpUserDataFactory
   */
  @Override
  public SnmpUserDataFactory getUserDataFactory() {
    return userDataFactory;
  }

  /**
   * Returns <CODE>true</CODE> if authentication traps are enabled.
   * <P>
   * When this feature is enabled, the SNMP protocol adaptor sends
   * an <CODE>authenticationFailure</CODE> trap each time an
   * authentication fails.
   * <P>
   * The default behaviour is to send authentication traps.
   *
   * @return <CODE>true</CODE> if authentication traps are enabled, <CODE>false</CODE> otherwise.
   */
  @Override
  public boolean getAuthTrapEnabled() {
    return authTrapEnabled;
  }

  /**
   * Sets the flag indicating if traps need to be sent in case of
   * authentication failure.
   *
   * @param enabled Flag indicating if traps need to be sent.
   */
  @Override
  public void setAuthTrapEnabled(boolean enabled) {
    authTrapEnabled = enabled;
  }

  /**
   * Returns <code>true</code> if this SNMP protocol adaptor sends a
   * response in case of authentication failure.
   * <P>
   * When this feature is enabled, the SNMP protocol adaptor sends a
   * response with <CODE>noSuchName</CODE> or <CODE>readOnly</CODE> when
   * the authentication failed. If the flag is disabled, the
   * SNMP protocol adaptor trashes the PDU silently.
   * <P>
   * The default behavior is to send responses.
   *
   * @return <CODE>true</CODE> if responses are sent.
   */
  @Override
  public boolean getAuthRespEnabled() {
    return authRespEnabled;
  }

  /**
   * Sets the flag indicating if responses need to be sent in case of
   * authentication failure.
   *
   * @param enabled Flag indicating if responses need to be sent.
   */
  @Override
  public void setAuthRespEnabled(boolean enabled) {
    authRespEnabled = enabled;
  }

  /**
   * Returns the enterprise OID. It is used by
   * {@link #snmpV1Trap snmpV1Trap} to fill the 'enterprise' field of the
   * trap request.
   *
   * @return The OID in string format "x.x.x.x".
   */
  @Override
  public String getEnterpriseOid() {
    return enterpriseOid.toString();
  }

  /**
   * Sets the enterprise OID.
   *
   * @param oid The OID in string format "x.x.x.x".
   * @throws IllegalArgumentException The string format is incorrect
   */
  @Override
  public void setEnterpriseOid(String oid) throws IllegalArgumentException {
    enterpriseOid = new SnmpOid(oid);
  }

  /**
   * Returns the names of the MIBs available in this SNMP protocol adaptor.
   *
   * @return An array of MIB names.
   */
  @Override
  public String[] getMibs() {
    String[] result = new String[mibs.size()];
    int i = 0;
    for (Enumeration<SnmpMibAgent> e = mibs.elements(); e.hasMoreElements(); ) {
      SnmpMibAgent mib = e.nextElement();
      result[i++] = mib.getMibName();
    }
    return result;
  }

  // GETTERS FOR SNMP GROUP (MIBII)
  //-------------------------------

  /**
   * Returns the <CODE>snmpOutTraps</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutTraps</CODE> value.
   */
  @Override
  public Long getSnmpOutTraps() {
    return new Long(snmpOutTraps);
  }

  /**
   * Returns the <CODE>snmpOutGetResponses</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutGetResponses</CODE> value.
   */
  @Override
  public Long getSnmpOutGetResponses() {
    return new Long(snmpOutGetResponses);
  }

  /**
   * Returns the <CODE>snmpOutGenErrs</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutGenErrs</CODE> value.
   */
  @Override
  public Long getSnmpOutGenErrs() {
    return new Long(snmpOutGenErrs);
  }

  /**
   * Returns the <CODE>snmpOutBadValues</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutBadValues</CODE> value.
   */
  @Override
  public Long getSnmpOutBadValues() {
    return new Long(snmpOutBadValues);
  }

  /**
   * Returns the <CODE>snmpOutNoSuchNames</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutNoSuchNames</CODE> value.
   */
  @Override
  public Long getSnmpOutNoSuchNames() {
    return new Long(snmpOutNoSuchNames);
  }

  /**
   * Returns the <CODE>snmpOutTooBigs</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutTooBigs</CODE> value.
   */
  @Override
  public Long getSnmpOutTooBigs() {
    return new Long(snmpOutTooBigs);
  }

  /**
   * Returns the <CODE>snmpInASNParseErrs</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInASNParseErrs</CODE> value.
   */
  @Override
  public Long getSnmpInASNParseErrs() {
    return new Long(snmpInASNParseErrs);
  }

  /**
   * Returns the <CODE>snmpInBadCommunityUses</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInBadCommunityUses</CODE> value.
   */
  @Override
  public Long getSnmpInBadCommunityUses() {
    return new Long(snmpInBadCommunityUses);
  }

  /**
   * Returns the <CODE>snmpInBadCommunityNames</CODE> value defined in
   * MIB-II.
   *
   * @return The <CODE>snmpInBadCommunityNames</CODE> value.
   */
  @Override
  public Long getSnmpInBadCommunityNames() {
    return new Long(snmpInBadCommunityNames);
  }

  /**
   * Returns the <CODE>snmpInBadVersions</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInBadVersions</CODE> value.
   */
  @Override
  public Long getSnmpInBadVersions() {
    return new Long(snmpInBadVersions);
  }

  /**
   * Returns the <CODE>snmpOutPkts</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpOutPkts</CODE> value.
   */
  @Override
  public Long getSnmpOutPkts() {
    return new Long(snmpOutPkts);
  }

  /**
   * Returns the <CODE>snmpInPkts</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInPkts</CODE> value.
   */
  @Override
  public Long getSnmpInPkts() {
    return new Long(snmpInPkts);
  }

  /**
   * Returns the <CODE>snmpInGetRequests</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInGetRequests</CODE> value.
   */
  @Override
  public Long getSnmpInGetRequests() {
    return new Long(snmpInGetRequests);
  }

  /**
   * Returns the <CODE>snmpInGetNexts</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInGetNexts</CODE> value.
   */
  @Override
  public Long getSnmpInGetNexts() {
    return new Long(snmpInGetNexts);
  }

  /**
   * Returns the <CODE>snmpInSetRequests</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInSetRequests</CODE> value.
   */
  @Override
  public Long getSnmpInSetRequests() {
    return new Long(snmpInSetRequests);
  }

  /**
   * Returns the <CODE>snmpInTotalSetVars</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInTotalSetVars</CODE> value.
   */
  @Override
  public Long getSnmpInTotalSetVars() {
    return new Long(snmpInTotalSetVars);
  }

  /**
   * Returns the <CODE>snmpInTotalReqVars</CODE> value defined in MIB-II.
   *
   * @return The <CODE>snmpInTotalReqVars</CODE> value.
   */
  @Override
  public Long getSnmpInTotalReqVars() {
    return new Long(snmpInTotalReqVars);
  }

  /**
   * Returns the <CODE>snmpSilentDrops</CODE> value defined in RFC
   * 1907 NMPv2-MIB .
   *
   * @return The <CODE>snmpSilentDrops</CODE> value.
   * @since 1.5
   */
  @Override
  public Long getSnmpSilentDrops() {
    return new Long(snmpSilentDrops);
  }

  /**
   * Returns the <CODE>snmpProxyDrops</CODE> value defined in RFC
   * 1907 NMPv2-MIB .
   *
   * @return The <CODE>snmpProxyDrops</CODE> value.
   * @since 1.5
   */
  @Override
  public Long getSnmpProxyDrops() {
    return new Long(0);
  }

  // PUBLIC METHODS
  //---------------

  /**
   * Allows the MBean to perform any operations it needs before being
   * registered in the MBean server.
   * If the name of the SNMP protocol adaptor MBean is not specified,
   * it is initialized with the default value:
   * {@link com.sun.jmx.snmp.ServiceName#DOMAIN
   * com.sun.jmx.snmp.ServiceName.DOMAIN}:{@link
   * com.sun.jmx.snmp.ServiceName#SNMP_ADAPTOR_SERVER
   * com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_SERVER}.
   * If any exception is raised, the SNMP protocol adaptor MBean will
   * not be registered in the MBean server.
   *
   * @param server The MBean server to register the service with.
   * @param name The object name.
   * @return The name of the SNMP protocol adaptor registered.
   */
  @Override
  public ObjectName preRegister(MBeanServer server, ObjectName name)
      throws java.lang.Exception {

    if (name == null) {
      name = new ObjectName(server.getDefaultDomain() + ":" +
          com.sun.jmx.snmp.ServiceName.SNMP_ADAPTOR_SERVER);
    }
    return (super.preRegister(server, name));
  }

  /**
   * Not used in this context.
   */
  @Override
  public void postRegister(Boolean registrationDone) {
    super.postRegister(registrationDone);
  }

  /**
   * Not used in this context.
   */
  @Override
  public void preDeregister() throws java.lang.Exception {
    super.preDeregister();
  }

  /**
   * Not used in this context.
   */
  @Override
  public void postDeregister() {
    super.postDeregister();
  }

  /**
   * Adds a new MIB in the SNMP MIB handler.
   *
   * @param mib The MIB to add.
   * @return A reference to the SNMP MIB handler.
   * @throws IllegalArgumentException If the parameter is null.
   */
  @Override
  public SnmpMibHandler addMib(SnmpMibAgent mib)
      throws IllegalArgumentException {
    if (mib == null) {
      throw new IllegalArgumentException();
    }

    if (!mibs.contains(mib)) {
      mibs.addElement(mib);
    }

    root.register(mib);

    return this;
  }

  /**
   * Adds a new MIB in the SNMP MIB handler.
   * This method is to be called to set a specific agent to a specific OID.
   * This can be useful when dealing with MIB overlapping.
   * Some OID can be implemented in more than one MIB. In this case,
   * the OID nearer agent will be used on SNMP operations.
   *
   * @param mib The MIB to add.
   * @param oids The set of OIDs this agent implements.
   * @return A reference to the SNMP MIB handler.
   * @throws IllegalArgumentException If the parameter is null.
   * @since 1.5
   */
  @Override
  public SnmpMibHandler addMib(SnmpMibAgent mib, SnmpOid[] oids)
      throws IllegalArgumentException {
    if (mib == null) {
      throw new IllegalArgumentException();
    }

    //If null oid array, just add it to the mib.
    if (oids == null) {
      return addMib(mib);
    }

    if (!mibs.contains(mib)) {
      mibs.addElement(mib);
    }

    for (int i = 0; i < oids.length; i++) {
      root.register(mib, oids[i].longValue());
    }
    return this;
  }

  /**
   * Adds a new MIB in the SNMP MIB handler. In SNMP V1 and V2 the
   * <CODE>contextName</CODE> is useless and this method
   * is equivalent to <CODE>addMib(SnmpMibAgent mib)</CODE>.
   *
   * @param mib The MIB to add.
   * @param contextName The MIB context name.
   * @return A reference on the SNMP MIB handler.
   * @throws IllegalArgumentException If the parameter is null.
   * @since 1.5
   */
  @Override
  public SnmpMibHandler addMib(SnmpMibAgent mib, String contextName)
      throws IllegalArgumentException {
    return addMib(mib);
  }

  /**
   * Adds a new MIB in the SNMP MIB handler. In SNMP V1 and V2 the
   * <CODE>contextName</CODE> is useless and this method
   * is equivalent to <CODE>addMib(SnmpMibAgent mib, SnmpOid[] oids)</CODE>.
   *
   * @param mib The MIB to add.
   * @param contextName The MIB context. If null is passed, will be registered in the default
   * context.
   * @param oids The set of OIDs this agent implements.
   * @return A reference to the SNMP MIB handler.
   * @throws IllegalArgumentException If the parameter is null.
   * @since 1.5
   */
  @Override
  public SnmpMibHandler addMib(SnmpMibAgent mib,
      String contextName,
      SnmpOid[] oids)
      throws IllegalArgumentException {

    return addMib(mib, oids);
  }

  /**
   * Removes the specified MIB from the SNMP protocol adaptor.
   * In SNMP V1 and V2 the <CODE>contextName</CODE> is useless and this
   * method is equivalent to <CODE>removeMib(SnmpMibAgent mib)</CODE>.
   *
   * @param mib The MIB to be removed.
   * @param contextName The context name used at registration time.
   * @return <CODE>true</CODE> if the specified <CODE>mib</CODE> was a MIB included in the SNMP MIB
   * handler, <CODE>false</CODE> otherwise.
   * @since 1.5
   */
  @Override
  public boolean removeMib(SnmpMibAgent mib, String contextName) {
    return removeMib(mib);
  }

  /**
   * Removes the specified MIB from the SNMP protocol adaptor.
   *
   * @param mib The MIB to be removed.
   * @return <CODE>true</CODE> if the specified <CODE>mib</CODE> was a MIB included in the SNMP MIB
   * handler, <CODE>false</CODE> otherwise.
   */
  @Override
  public boolean removeMib(SnmpMibAgent mib) {
    root.unregister(mib);
    return (mibs.removeElement(mib));
  }

  /**
   * Removes the specified MIB from the SNMP protocol adaptor.
   *
   * @param mib The MIB to be removed.
   * @param oids The oid the MIB was previously registered for.
   * @return <CODE>true</CODE> if the specified <CODE>mib</CODE> was a MIB included in the SNMP MIB
   * handler, <CODE>false</CODE> otherwise.
   * @since 1.5
   */
  @Override
  public boolean removeMib(SnmpMibAgent mib, SnmpOid[] oids) {
    root.unregister(mib, oids);
    return (mibs.removeElement(mib));
  }

  /**
   * Removes the specified MIB from the SNMP protocol adaptor.
   *
   * @param mib The MIB to be removed.
   * @param contextName The context name used at registration time.
   * @param oids The oid the MIB was previously registered for.
   * @return <CODE>true</CODE> if the specified <CODE>mib</CODE> was a MIB included in the SNMP MIB
   * handler, <CODE>false</CODE> otherwise.
   * @since 1.5
   */
  @Override
  public boolean removeMib(SnmpMibAgent mib,
      String contextName,
      SnmpOid[] oids) {
    return removeMib(mib, oids);
  }

  // SUBCLASSING OF COMMUNICATOR SERVER
  //-----------------------------------

  /**
   * Creates the datagram socket.
   */
  @Override
  protected void doBind()
      throws CommunicationException, InterruptedException {

    try {
      synchronized (this) {
        socket = new DatagramSocket(port, address);
      }
      dbgTag = makeDebugTag();
    } catch (SocketException e) {
      if (e.getMessage().equals(InterruptSysCallMsg)) {
        throw new InterruptedException(e.toString());
      } else {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "doBind", "cannot bind on port " + port);
        }
        throw new CommunicationException(e);
      }
    }
  }

  /**
   * Return the actual port to which the adaptor is bound.
   * Can be different from the port given at construction time if
   * that port number was 0.
   *
   * @return the actual port to which the adaptor is bound.
   **/
  @Override
  public int getPort() {
    synchronized (this) {
      if (socket != null) {
        return socket.getLocalPort();
      }
    }
    return super.getPort();
  }

  /**
   * Closes the datagram socket.
   */
  @Override
  protected void doUnbind()
      throws CommunicationException, InterruptedException {
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "doUnbind", "Finally close the socket");
    }
    synchronized (this) {
      if (socket != null) {
        socket.close();
        socket = null;
        // Important to inform finalize() that the socket is closed...
      }
    }
    closeTrapSocketIfNeeded();
    closeInformSocketIfNeeded();
  }

  private void createSnmpRequestHandler(SnmpAdaptorServer server,
      int id,
      DatagramSocket s,
      DatagramPacket p,
      SnmpMibTree tree,
      Vector<SnmpMibAgent> m,
      InetAddressAcl a,
      SnmpPduFactory factory,
      SnmpUserDataFactory dataFactory,
      MBeanServer f,
      ObjectName n) {
    final SnmpRequestHandler handler =
        new SnmpRequestHandler(this, id, s, p, tree, m, a, factory,
            dataFactory, f, n);
    threadService.submitTask(handler);
  }

  /**
   * Reads a packet from the datagram socket and creates a request
   * handler which decodes and processes the request.
   */
  @Override
  protected void doReceive()
      throws CommunicationException, InterruptedException {

    // Let's wait for something to be received.
    //
    try {
      packet = new DatagramPacket(new byte[bufferSize], bufferSize);
      socket.receive(packet);
      int state = getState();

      if (state != ONLINE) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
              "doReceive", "received a message but state not online, returning.");
        }
        return;
      }

      createSnmpRequestHandler(this, servedClientCount, socket,
          packet, root, mibs, ipacl, pduFactory,
          userDataFactory, topMBS, objectName);
    } catch (SocketException e) {
      // Let's check if we have been interrupted by stop().
      //
      if (e.getMessage().equals(InterruptSysCallMsg)) {
        throw new InterruptedException(e.toString());
      } else {
        throw new CommunicationException(e);
      }
    } catch (InterruptedIOException e) {
      throw new InterruptedException(e.toString());
    } catch (CommunicationException e) {
      throw e;
    } catch (Exception e) {
      throw new CommunicationException(e);
    }
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "doReceive", "received a message");
    }
  }

  @Override
  protected void doError(Exception e) throws CommunicationException {
  }

  /**
   * Not used in this context.
   */
  @Override
  protected void doProcess()
      throws CommunicationException, InterruptedException {
  }


  /**
   * The number of times the communicator server will attempt
   * to bind before giving up.
   * We attempt only once...
   *
   * @return 1
   **/
  @Override
  protected int getBindTries() {
    return 1;
  }

  /**
   * Stops this SNMP protocol adaptor.
   * Closes the datagram socket.
   * <p>
   * Has no effect if this SNMP protocol adaptor is <CODE>OFFLINE</CODE> or
   * <CODE>STOPPING</CODE>.
   */
  @Override
  public void stop() {

    final int port = getPort();
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "stop", "Stopping: using port " + port);
    }
    if ((state == ONLINE) || (state == STARTING)) {
      super.stop();
      try {
        DatagramSocket sn = new DatagramSocket(0);
        try {
          byte[] ob = new byte[1];

          DatagramPacket pk;
          if (address != null) {
            pk = new DatagramPacket(ob, 1, address, port);
          } else {
            pk = new DatagramPacket(ob, 1,
                java.net.InetAddress.getLocalHost(), port);
          }

          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
                "stop", "Sending: using port " + port);
          }
          sn.send(pk);
        } finally {
          sn.close();
        }
      } catch (Throwable e) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "stop", "Got unexpected Throwable", e);
        }
      }
    }
  }

  // SENDING SNMP TRAPS STUFF
  //-------------------------

  /**
   * Sends a trap using SNMP V1 trap format.
   * <BR>The trap is sent to each destination defined in the ACL file
   * (if available).
   * If no ACL file or no destinations are available, the trap is sent
   * to the local host.
   *
   * @param generic The generic number of the trap.
   * @param specific The specific number of the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   */
  @Override
  public void snmpV1Trap(int generic, int specific,
      SnmpVarBindList varBindList)
      throws IOException, SnmpStatusException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpV1Trap", "generic=" + generic +
              ", specific=" + specific);
    }

    // First, make an SNMP V1 trap pdu
    //
    SnmpPduTrap pdu = new SnmpPduTrap();
    pdu.address = null;
    pdu.port = trapPort;
    pdu.type = pduV1TrapPdu;
    pdu.version = snmpVersionOne;
    pdu.community = null;
    pdu.enterprise = enterpriseOid;
    pdu.genericTrap = generic;
    pdu.specificTrap = specific;
    pdu.timeStamp = getSysUpTime();

    if (varBindList != null) {
      pdu.varBindList = new SnmpVarBind[varBindList.size()];
      varBindList.copyInto(pdu.varBindList);
    } else {
      pdu.varBindList = null;
    }

    // If the local host cannot be determined, we put 0.0.0.0 in agentAddr
    try {
      if (address != null) {
        pdu.agentAddr = handleMultipleIpVersion(address.getAddress());
      } else {
        pdu.agentAddr =
            handleMultipleIpVersion(InetAddress.getLocalHost().getAddress());
      }
    } catch (UnknownHostException e) {
      byte[] zeroedAddr = new byte[4];
      pdu.agentAddr = handleMultipleIpVersion(zeroedAddr);
    }

    // Next, send the pdu to all destinations defined in ACL
    //
    sendTrapPdu(pdu);
  }

  private SnmpIpAddress handleMultipleIpVersion(byte[] address) {
    if (address.length == 4) {
      return new SnmpIpAddress(address);
    } else {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "handleMultipleIPVersion",
            "Not an IPv4 address, return null");
      }
      return null;
    }
  }

  /**
   * Sends a trap using SNMP V1 trap format.
   * <BR>The trap is sent to the specified <CODE>InetAddress</CODE>
   * destination using the specified community string (and the ACL file
   * is not used).
   *
   * @param addr The <CODE>InetAddress</CODE> destination of the trap.
   * @param cs The community string to be used for the trap.
   * @param generic The generic number of the trap.
   * @param specific The specific number of the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   */
  @Override
  public void snmpV1Trap(InetAddress addr, String cs, int generic,
      int specific, SnmpVarBindList varBindList)
      throws IOException, SnmpStatusException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpV1Trap", "generic=" + generic + ", specific=" +
              specific);
    }

    // First, make an SNMP V1 trap pdu
    //
    SnmpPduTrap pdu = new SnmpPduTrap();
    pdu.address = null;
    pdu.port = trapPort;
    pdu.type = pduV1TrapPdu;
    pdu.version = snmpVersionOne;

    if (cs != null) {
      pdu.community = cs.getBytes();
    } else {
      pdu.community = null;
    }

    pdu.enterprise = enterpriseOid;
    pdu.genericTrap = generic;
    pdu.specificTrap = specific;
    pdu.timeStamp = getSysUpTime();

    if (varBindList != null) {
      pdu.varBindList = new SnmpVarBind[varBindList.size()];
      varBindList.copyInto(pdu.varBindList);
    } else {
      pdu.varBindList = null;
    }

    // If the local host cannot be determined, we put 0.0.0.0 in agentAddr
    try {
      if (address != null) {
        pdu.agentAddr = handleMultipleIpVersion(address.getAddress());
      } else {
        pdu.agentAddr =
            handleMultipleIpVersion(InetAddress.getLocalHost().getAddress());
      }
    } catch (UnknownHostException e) {
      byte[] zeroedAddr = new byte[4];
      pdu.agentAddr = handleMultipleIpVersion(zeroedAddr);
    }

    // Next, send the pdu to the specified destination
    //
    if (addr != null) {
      sendTrapPdu(addr, pdu);
    } else {
      sendTrapPdu(pdu);
    }
  }

  /**
   * Sends a trap using SNMP V1 trap format.
   * <BR>The trap is sent to the specified <CODE>InetAddress</CODE>
   * destination using the specified parameters (and the ACL file is not
   * used).
   * Note that if the specified <CODE>InetAddress</CODE> destination is null,
   * then the ACL file mechanism is used.
   *
   * @param addr The <CODE>InetAddress</CODE> destination of the trap.
   * @param agentAddr The agent address to be used for the trap.
   * @param cs The community string to be used for the trap.
   * @param enterpOid The enterprise OID to be used for the trap.
   * @param generic The generic number of the trap.
   * @param specific The specific number of the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @param time The time stamp (overwrite the current time).
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  public void snmpV1Trap(InetAddress addr,
      SnmpIpAddress agentAddr,
      String cs,
      SnmpOid enterpOid,
      int generic,
      int specific,
      SnmpVarBindList varBindList,
      SnmpTimeticks time)
      throws IOException, SnmpStatusException {
    snmpV1Trap(addr,
        trapPort,
        agentAddr,
        cs,
        enterpOid,
        generic,
        specific,
        varBindList,
        time);
  }

  /**
   * Sends a trap using SNMP V1 trap format.
   * <BR>The trap is sent to the specified <CODE>SnmpPeer</CODE> destination.
   * The community string used is the one located in the
   * <CODE>SnmpPeer</CODE> parameters
   * (<CODE>SnmpParameters.getRdCommunity() </CODE>).
   *
   * @param peer The <CODE>SnmpPeer</CODE> destination of the trap.
   * @param agentAddr The agent address to be used for the trap.
   * @param enterpOid The enterprise OID to be used for the trap.
   * @param generic The generic number of the trap.
   * @param specific The specific number of the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @param time The time stamp (overwrite the current time).
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  @Override
  public void snmpV1Trap(SnmpPeer peer,
      SnmpIpAddress agentAddr,
      SnmpOid enterpOid,
      int generic,
      int specific,
      SnmpVarBindList varBindList,
      SnmpTimeticks time)
      throws IOException, SnmpStatusException {

    SnmpParameters p = (SnmpParameters) peer.getParams();
    snmpV1Trap(peer.getDestAddr(),
        peer.getDestPort(),
        agentAddr,
        p.getRdCommunity(),
        enterpOid,
        generic,
        specific,
        varBindList,
        time);
  }

  private void snmpV1Trap(InetAddress addr,
      int port,
      SnmpIpAddress agentAddr,
      String cs,
      SnmpOid enterpOid,
      int generic,
      int specific,
      SnmpVarBindList varBindList,
      SnmpTimeticks time)
      throws IOException, SnmpStatusException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpV1Trap", "generic=" + generic + ", specific=" +
              specific);
    }

    // First, make an SNMP V1 trap pdu
    //
    SnmpPduTrap pdu = new SnmpPduTrap();
    pdu.address = null;
    pdu.port = port;
    pdu.type = pduV1TrapPdu;
    pdu.version = snmpVersionOne;

    //Diff start
    if (cs != null) {
      pdu.community = cs.getBytes();
    } else {
      pdu.community = null;
    }
    //Diff end

    // Diff start
    if (enterpOid != null) {
      pdu.enterprise = enterpOid;
    } else {
      pdu.enterprise = enterpriseOid;
    }
    //Diff end
    pdu.genericTrap = generic;
    pdu.specificTrap = specific;
    //Diff start
    if (time != null) {
      pdu.timeStamp = time.longValue();
    } else {
      pdu.timeStamp = getSysUpTime();
    }
    //Diff end

    if (varBindList != null) {
      pdu.varBindList = new SnmpVarBind[varBindList.size()];
      varBindList.copyInto(pdu.varBindList);
    } else {
      pdu.varBindList = null;
    }

    if (agentAddr == null) {
      // If the local host cannot be determined,
      // we put 0.0.0.0 in agentAddr
      try {
        final InetAddress inetAddr =
            (address != null) ? address : InetAddress.getLocalHost();
        agentAddr = handleMultipleIpVersion(inetAddr.getAddress());
      } catch (UnknownHostException e) {
        byte[] zeroedAddr = new byte[4];
        agentAddr = handleMultipleIpVersion(zeroedAddr);
      }
    }

    pdu.agentAddr = agentAddr;

    // Next, send the pdu to the specified destination
    //
    // Diff start
    if (addr != null) {
      sendTrapPdu(addr, pdu);
    } else {
      sendTrapPdu(pdu);
    }

    //End diff
  }

  /**
   * Sends a trap using SNMP V2 trap format.
   * <BR>The trap is sent to the specified <CODE>SnmpPeer</CODE> destination.
   * <BR>The community string used is the one located in the
   * <CODE>SnmpPeer</CODE> parameters
   * (<CODE>SnmpParameters.getRdCommunity() </CODE>).
   * <BR>The variable list included in the outgoing trap is composed of
   * the following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with the value specified by
   * <CODE>time</CODE></LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   *
   * @param peer The <CODE>SnmpPeer</CODE> destination of the trap.
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @param time The time stamp (overwrite the current time).
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  @Override
  public void snmpV2Trap(SnmpPeer peer,
      SnmpOid trapOid,
      SnmpVarBindList varBindList,
      SnmpTimeticks time)
      throws IOException, SnmpStatusException {

    SnmpParameters p = (SnmpParameters) peer.getParams();
    snmpV2Trap(peer.getDestAddr(),
        peer.getDestPort(),
        p.getRdCommunity(),
        trapOid,
        varBindList,
        time);
  }

  /**
   * Sends a trap using SNMP V2 trap format.
   * <BR>The trap is sent to each destination defined in the ACL file
   * (if available). If no ACL file or no destinations are available,
   * the trap is sent to the local host.
   * <BR>The variable list included in the outgoing trap is composed of
   * the following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with its current value</LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   *
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   */
  @Override
  public void snmpV2Trap(SnmpOid trapOid, SnmpVarBindList varBindList)
      throws IOException, SnmpStatusException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpV2Trap", "trapOid=" + trapOid);
    }

    // First, make an SNMP V2 trap pdu
    // We clone varBindList and insert sysUpTime and snmpTrapOid
    //
    SnmpPduRequest pdu = new SnmpPduRequest();
    pdu.address = null;
    pdu.port = trapPort;
    pdu.type = pduV2TrapPdu;
    pdu.version = snmpVersionTwo;
    pdu.community = null;

    SnmpVarBindList fullVbl;
    if (varBindList != null) {
      fullVbl = varBindList.clone();
    } else {
      fullVbl = new SnmpVarBindList(2);
    }
    SnmpTimeticks sysUpTimeValue = new SnmpTimeticks(getSysUpTime());
    fullVbl.insertElementAt(new SnmpVarBind(snmpTrapOidOid, trapOid), 0);
    fullVbl.insertElementAt(new SnmpVarBind(sysUpTimeOid, sysUpTimeValue),
        0);
    pdu.varBindList = new SnmpVarBind[fullVbl.size()];
    fullVbl.copyInto(pdu.varBindList);

    // Next, send the pdu to all destinations defined in ACL
    //
    sendTrapPdu(pdu);
  }

  /**
   * Sends a trap using SNMP V2 trap format.
   * <BR>The trap is sent to the specified <CODE>InetAddress</CODE>
   * destination using the specified community string (and the ACL file
   * is not used).
   * <BR>The variable list included in the outgoing trap is composed of
   * the following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with its current value</LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   *
   * @param addr The <CODE>InetAddress</CODE> destination of the trap.
   * @param cs The community string to be used for the trap.
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   */
  @Override
  public void snmpV2Trap(InetAddress addr, String cs, SnmpOid trapOid,
      SnmpVarBindList varBindList)
      throws IOException, SnmpStatusException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpV2Trap", "trapOid=" + trapOid);
    }

    // First, make an SNMP V2 trap pdu
    // We clone varBindList and insert sysUpTime and snmpTrapOid
    //
    SnmpPduRequest pdu = new SnmpPduRequest();
    pdu.address = null;
    pdu.port = trapPort;
    pdu.type = pduV2TrapPdu;
    pdu.version = snmpVersionTwo;

    if (cs != null) {
      pdu.community = cs.getBytes();
    } else {
      pdu.community = null;
    }

    SnmpVarBindList fullVbl;
    if (varBindList != null) {
      fullVbl = varBindList.clone();
    } else {
      fullVbl = new SnmpVarBindList(2);
    }
    SnmpTimeticks sysUpTimeValue = new SnmpTimeticks(getSysUpTime());
    fullVbl.insertElementAt(new SnmpVarBind(snmpTrapOidOid, trapOid), 0);
    fullVbl.insertElementAt(new SnmpVarBind(sysUpTimeOid, sysUpTimeValue),
        0);
    pdu.varBindList = new SnmpVarBind[fullVbl.size()];
    fullVbl.copyInto(pdu.varBindList);

    // Next, send the pdu to the specified destination
    //
    if (addr != null) {
      sendTrapPdu(addr, pdu);
    } else {
      sendTrapPdu(pdu);
    }
  }

  /**
   * Sends a trap using SNMP V2 trap format.
   * <BR>The trap is sent to the specified <CODE>InetAddress</CODE>
   * destination using the specified parameters (and the ACL file is not
   * used).
   * Note that if the specified <CODE>InetAddress</CODE> destination is null,
   * then the ACL file mechanism is used.
   * <BR>The variable list included in the outgoing trap is composed of the
   * following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with the value specified by
   * <CODE>time</CODE></LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   *
   * @param addr The <CODE>InetAddress</CODE> destination of the trap.
   * @param cs The community string to be used for the trap.
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @param time The time stamp (overwrite the current time).
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  public void snmpV2Trap(InetAddress addr,
      String cs,
      SnmpOid trapOid,
      SnmpVarBindList varBindList,
      SnmpTimeticks time)
      throws IOException, SnmpStatusException {

    snmpV2Trap(addr,
        trapPort,
        cs,
        trapOid,
        varBindList,
        time);
  }

  private void snmpV2Trap(InetAddress addr,
      int port,
      String cs,
      SnmpOid trapOid,
      SnmpVarBindList varBindList,
      SnmpTimeticks time)
      throws IOException, SnmpStatusException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      final StringBuilder strb = new StringBuilder()
          .append("trapOid=").append(trapOid)
          .append("\ncommunity=").append(cs)
          .append("\naddr=").append(addr)
          .append("\nvarBindList=").append(varBindList)
          .append("\ntime=").append(time)
          .append("\ntrapPort=").append(port);
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpV2Trap", strb.toString());
    }

    // First, make an SNMP V2 trap pdu
    // We clone varBindList and insert sysUpTime and snmpTrapOid
    //
    SnmpPduRequest pdu = new SnmpPduRequest();
    pdu.address = null;
    pdu.port = port;
    pdu.type = pduV2TrapPdu;
    pdu.version = snmpVersionTwo;

    if (cs != null) {
      pdu.community = cs.getBytes();
    } else {
      pdu.community = null;
    }

    SnmpVarBindList fullVbl;
    if (varBindList != null) {
      fullVbl = varBindList.clone();
    } else {
      fullVbl = new SnmpVarBindList(2);
    }

    // Only difference with other
    SnmpTimeticks sysUpTimeValue;
    if (time != null) {
      sysUpTimeValue = time;
    } else {
      sysUpTimeValue = new SnmpTimeticks(getSysUpTime());
    }
    //End of diff

    fullVbl.insertElementAt(new SnmpVarBind(snmpTrapOidOid, trapOid), 0);
    fullVbl.insertElementAt(new SnmpVarBind(sysUpTimeOid, sysUpTimeValue),
        0);
    pdu.varBindList = new SnmpVarBind[fullVbl.size()];
    fullVbl.copyInto(pdu.varBindList);

    // Next, send the pdu to the specified destination
    //
    // Diff start
    if (addr != null) {
      sendTrapPdu(addr, pdu);
    } else {
      sendTrapPdu(pdu);
    }
    //End diff
  }

  /**
   * Send the specified trap PDU to the passed <CODE>InetAddress</CODE>.
   *
   * @param address The destination address.
   * @param pdu The pdu to send.
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  @Override
  public void snmpPduTrap(InetAddress address, SnmpPduPacket pdu)
      throws IOException, SnmpStatusException {

    if (address != null) {
      sendTrapPdu(address, pdu);
    } else {
      sendTrapPdu(pdu);
    }
  }

  /**
   * Send the specified trap PDU to the passed <CODE>SnmpPeer</CODE>.
   *
   * @param peer The destination peer. The Read community string is used of
   * <CODE>SnmpParameters</CODE> is used as the trap community string.
   * @param pdu The pdu to send.
   * @throws IOException An I/O error occurred while sending the trap.
   * @throws SnmpStatusException If the trap exceeds the limit defined by <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  @Override
  public void snmpPduTrap(SnmpPeer peer,
      SnmpPduPacket pdu)
      throws IOException, SnmpStatusException {
    if (peer != null) {
      pdu.port = peer.getDestPort();
      sendTrapPdu(peer.getDestAddr(), pdu);
    } else {
      pdu.port = getTrapPort().intValue();
      sendTrapPdu(pdu);
    }
  }

  /**
   * Send the specified trap PDU to every destinations from the ACL file.
   */
  private void sendTrapPdu(SnmpPduPacket pdu)
      throws SnmpStatusException, IOException {

    // Make an SNMP message from the pdu
    //
    SnmpMessage msg = null;
    try {
      msg = (SnmpMessage) pduFactory.encodeSnmpPdu(pdu, bufferSize);
      if (msg == null) {
        throw new SnmpStatusException(
            SnmpDefinitions.snmpRspAuthorizationError);
      }
    } catch (SnmpTooBigException x) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "sendTrapPdu", "Trap pdu is too big. " +
                "Trap hasn't been sent to anyone");
      }
      throw new SnmpStatusException(SnmpDefinitions.snmpRspTooBig);
      // FIXME: is the right exception to throw ?
      // We could simply forward SnmpTooBigException ?
    }

    // Now send the SNMP message to each destination
    //
    int sendingCount = 0;
    openTrapSocketIfNeeded();
    if (ipacl != null) {
      Enumeration<InetAddress> ed = ipacl.getTrapDestinations();
      while (ed.hasMoreElements()) {
        msg.address = ed.nextElement();
        Enumeration<String> ec = ipacl.getTrapCommunities(msg.address);
        while (ec.hasMoreElements()) {
          msg.community = ec.nextElement().getBytes();
          try {
            sendTrapMessage(msg);
            sendingCount++;
          } catch (SnmpTooBigException x) {
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
              SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                  "sendTrapPdu", "Trap pdu is too big. " +
                      "Trap hasn't been sent to " + msg.address);
            }
          }
        }
      }
    }

    // If there is no destination defined or if everything has failed
    // we tried to send the trap to the local host (as suggested by
    // mister Olivier Reisacher).
    //
    if (sendingCount == 0) {
      try {
        msg.address = InetAddress.getLocalHost();
        sendTrapMessage(msg);
      } catch (SnmpTooBigException x) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "sendTrapPdu", "Trap pdu is too big. " +
                  "Trap hasn't been sent.");
        }
      } catch (UnknownHostException e) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "sendTrapPdu", "Trap pdu is too big. " +
                  "Trap hasn't been sent.");
        }
      }
    }

    closeTrapSocketIfNeeded();
  }

  /**
   * Send the specified trap PDU to the specified destination.
   */
  private void sendTrapPdu(InetAddress addr, SnmpPduPacket pdu)
      throws SnmpStatusException, IOException {

    // Make an SNMP message from the pdu
    //
    SnmpMessage msg = null;
    try {
      msg = (SnmpMessage) pduFactory.encodeSnmpPdu(pdu, bufferSize);
      if (msg == null) {
        throw new SnmpStatusException(
            SnmpDefinitions.snmpRspAuthorizationError);
      }
    } catch (SnmpTooBigException x) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "sendTrapPdu", "Trap pdu is too big. " +
                "Trap hasn't been sent to the specified host.");
      }
      throw new SnmpStatusException(SnmpDefinitions.snmpRspTooBig);
      // FIXME: is the right exception to throw ?
      // We could simply forward SnmpTooBigException ?
    }

    // Now send the SNMP message to specified destination
    //
    openTrapSocketIfNeeded();
    if (addr != null) {
      msg.address = addr;
      try {
        sendTrapMessage(msg);
      } catch (SnmpTooBigException x) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "sendTrapPdu", "Trap pdu is too big. " +
                  "Trap hasn't been sent to " + msg.address);
        }
      }
    }

    closeTrapSocketIfNeeded();
  }

  /**
   * Send the specified message on trapSocket.
   */
  private void sendTrapMessage(SnmpMessage msg)
      throws IOException, SnmpTooBigException {

    byte[] buffer = new byte[bufferSize];
    DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    int encodingLength = msg.encodeMessage(buffer);
    packet.setLength(encodingLength);
    packet.setAddress(msg.address);
    packet.setPort(msg.port);
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "sendTrapMessage", "sending trap to " + msg.address + ":" +
              msg.port);
    }
    trapSocket.send(packet);
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "sendTrapMessage", "sent to " + msg.address + ":" +
              msg.port);
    }
    snmpOutTraps++;
    snmpOutPkts++;
  }

  /**
   * Open trapSocket if it's not already done.
   */
  synchronized void openTrapSocketIfNeeded() throws SocketException {
    if (trapSocket == null) {
      trapSocket = new DatagramSocket(0, address);
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
            "openTrapSocketIfNeeded", "using port " +
                trapSocket.getLocalPort() + " to send traps");
      }
    }
  }

  /**
   * Close trapSocket if the SNMP protocol adaptor is not ONLINE.
   */
  synchronized void closeTrapSocketIfNeeded() {
    if ((trapSocket != null) && (state != ONLINE)) {
      trapSocket.close();
      trapSocket = null;
    }
  }

  // SENDING SNMP INFORMS STUFF
  //---------------------------

  /**
   * Sends an inform using SNMP V2 inform request format.
   * <BR>The inform request is sent to each destination defined in the ACL
   * file (if available).
   * If no ACL file or no destinations are available, the inform request is
   * sent to the local host.
   * <BR>The variable list included in the outgoing inform is composed of
   * the following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with its current value</LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   * To send an inform request, the SNMP adaptor server must be active.
   *
   * @param cb The callback that is invoked when a request is complete.
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @return A vector of {@link com.sun.jmx.snmp.daemon.SnmpInformRequest} objects. <P>If there is
   * no destination host for this inform request, the returned vector will be empty.
   * @throws IllegalStateException This method has been invoked while the SNMP adaptor server was
   * not active.
   * @throws IOException An I/O error occurred while sending the inform request.
   * @throws SnmpStatusException If the inform request exceeds the limit defined by
   * <CODE>bufferSize</CODE>.
   */
  @Override
  public Vector<SnmpInformRequest> snmpInformRequest(SnmpInformHandler cb,
      SnmpOid trapOid,
      SnmpVarBindList varBindList)
      throws IllegalStateException, IOException, SnmpStatusException {

    if (!isActive()) {
      throw new IllegalStateException(
          "Start SNMP adaptor server before carrying out this operation");
    }
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpInformRequest", "trapOid=" + trapOid);
    }

    // First, make an SNMP inform pdu:
    // We clone varBindList and insert sysUpTime and snmpTrapOid variables.
    //
    SnmpVarBindList fullVbl;
    if (varBindList != null) {
      fullVbl = varBindList.clone();
    } else {
      fullVbl = new SnmpVarBindList(2);
    }
    SnmpTimeticks sysUpTimeValue = new SnmpTimeticks(getSysUpTime());
    fullVbl.insertElementAt(new SnmpVarBind(snmpTrapOidOid, trapOid), 0);
    fullVbl.insertElementAt(new SnmpVarBind(sysUpTimeOid, sysUpTimeValue),
        0);

    // Next, send the pdu to the specified destination
    //
    openInformSocketIfNeeded();

    // Now send the SNMP message to each destination
    //
    Vector<SnmpInformRequest> informReqList = new Vector<>();
    InetAddress addr;
    String cs;
    if (ipacl != null) {
      Enumeration<InetAddress> ed = ipacl.getInformDestinations();
      while (ed.hasMoreElements()) {
        addr = ed.nextElement();
        Enumeration<String> ec = ipacl.getInformCommunities(addr);
        while (ec.hasMoreElements()) {
          cs = ec.nextElement();
          informReqList.addElement(
              informSession.makeAsyncRequest(addr, cs, cb,
                  fullVbl, getInformPort()));
        }
      }
    }

    return informReqList;
  }

  /**
   * Sends an inform using SNMP V2 inform request format.
   * <BR>The inform is sent to the specified <CODE>InetAddress</CODE>
   * destination
   * using the specified community string.
   * <BR>The variable list included in the outgoing inform is composed
   * of the following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with its current value</LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   * To send an inform request, the SNMP adaptor server must be active.
   *
   * @param addr The <CODE>InetAddress</CODE> destination for this inform request.
   * @param cs The community string to be used for the inform request.
   * @param cb The callback that is invoked when a request is complete.
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @return The inform request object.
   * @throws IllegalStateException This method has been invoked while the SNMP adaptor server was
   * not active.
   * @throws IOException An I/O error occurred while sending the inform request.
   * @throws SnmpStatusException If the inform request exceeds the limit defined by
   * <CODE>bufferSize</CODE>.
   */
  @Override
  public SnmpInformRequest snmpInformRequest(InetAddress addr,
      String cs,
      SnmpInformHandler cb,
      SnmpOid trapOid,
      SnmpVarBindList varBindList)
      throws IllegalStateException, IOException, SnmpStatusException {

    return snmpInformRequest(addr,
        getInformPort(),
        cs,
        cb,
        trapOid,
        varBindList);
  }

  /**
   * Sends an inform using SNMP V2 inform request format.
   * <BR>The inform is sent to the specified <CODE>SnmpPeer</CODE>
   * destination.
   * <BR>The community string used is the one located in the
   * <CODE>SnmpPeer</CODE> parameters
   * (<CODE>SnmpParameters.getInformCommunity() </CODE>).
   * <BR>The variable list included in the outgoing inform is composed
   * of the following items:
   * <UL>
   * <LI><CODE>sysUpTime.0</CODE> with its current value</LI>
   * <LI><CODE>snmpTrapOid.0</CODE> with the value specified by
   * <CODE>trapOid</CODE></LI>
   * <LI><CODE>all the (oid,values)</CODE> from the specified
   * <CODE>varBindList</CODE></LI>
   * </UL>
   * To send an inform request, the SNMP adaptor server must be active.
   *
   * @param peer The <CODE>SnmpPeer</CODE> destination for this inform request.
   * @param cb The callback that is invoked when a request is complete.
   * @param trapOid The OID identifying the trap.
   * @param varBindList A list of <CODE>SnmpVarBind</CODE> instances or null.
   * @return The inform request object.
   * @throws IllegalStateException This method has been invoked while the SNMP adaptor server was
   * not active.
   * @throws IOException An I/O error occurred while sending the inform request.
   * @throws SnmpStatusException If the inform request exceeds the limit defined by
   * <CODE>bufferSize</CODE>.
   * @since 1.5
   */
  @Override
  public SnmpInformRequest snmpInformRequest(SnmpPeer peer,
      SnmpInformHandler cb,
      SnmpOid trapOid,
      SnmpVarBindList varBindList)
      throws IllegalStateException, IOException, SnmpStatusException {

    SnmpParameters p = (SnmpParameters) peer.getParams();
    return snmpInformRequest(peer.getDestAddr(),
        peer.getDestPort(),
        p.getInformCommunity(),
        cb,
        trapOid,
        varBindList);
  }

  /**
   * Method that maps an SNMP error status in the passed protocolVersion
   * according to the provided pdu type.
   *
   * @param errorStatus The error status to convert.
   * @param protocolVersion The protocol version.
   * @param reqPduType The pdu type.
   */
  public static int mapErrorStatus(int errorStatus,
      int protocolVersion,
      int reqPduType) {
    return SnmpSubRequestHandler.mapErrorStatus(errorStatus,
        protocolVersion,
        reqPduType);
  }

  private SnmpInformRequest snmpInformRequest(InetAddress addr,
      int port,
      String cs,
      SnmpInformHandler cb,
      SnmpOid trapOid,
      SnmpVarBindList varBindList)
      throws IllegalStateException, IOException, SnmpStatusException {

    if (!isActive()) {
      throw new IllegalStateException(
          "Start SNMP adaptor server before carrying out this operation");
    }
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "snmpInformRequest", "trapOid=" + trapOid);
    }

    // First, make an SNMP inform pdu:
    // We clone varBindList and insert sysUpTime and snmpTrapOid variables.
    //
    SnmpVarBindList fullVbl;
    if (varBindList != null) {
      fullVbl = varBindList.clone();
    } else {
      fullVbl = new SnmpVarBindList(2);
    }
    SnmpTimeticks sysUpTimeValue = new SnmpTimeticks(getSysUpTime());
    fullVbl.insertElementAt(new SnmpVarBind(snmpTrapOidOid, trapOid), 0);
    fullVbl.insertElementAt(new SnmpVarBind(sysUpTimeOid, sysUpTimeValue),
        0);

    // Next, send the pdu to the specified destination
    //
    openInformSocketIfNeeded();
    return informSession.makeAsyncRequest(addr, cs, cb, fullVbl, port);
  }


  /**
   * Open informSocket if it's not already done.
   */
  synchronized void openInformSocketIfNeeded() throws SocketException {
    if (informSession == null) {
      informSession = new SnmpSession(this);
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
            "openInformSocketIfNeeded",
            "to send inform requests and receive inform responses");
      }
    }
  }

  /**
   * Close informSocket if the SNMP protocol adaptor is not ONLINE.
   */
  synchronized void closeInformSocketIfNeeded() {
    if ((informSession != null) && (state != ONLINE)) {
      informSession.destroySession();
      informSession = null;
    }
  }

  /**
   * Gets the IP address to bind.
   * This getter is used to initialize the DatagramSocket in the
   * SnmpSocket object created for the inform request stuff.
   */
  InetAddress getAddress() {
    return address;
  }

  // PROTECTED METHODS
  //------------------

  /**
   * Finalizer of the SNMP protocol adaptor objects.
   * This method is called by the garbage collector on an object
   * when garbage collection determines that there are no more
   * references to the object.
   * <P>Closes the datagram socket associated to this SNMP protocol adaptor.
   */
  @Override
  protected void finalize() {
    try {
      if (socket != null) {
        socket.close();
        socket = null;
      }

      threadService.terminate();
    } catch (Exception e) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
            "finalize", "Exception in finalizer", e);
      }
    }
  }

  // PACKAGE METHODS
  //----------------

  /**
   * Returns the string used in debug traces.
   */
  @Override
  String makeDebugTag() {
    return "SnmpAdaptorServer[" + getProtocol() + ":" + getPort() + "]";
  }

  void updateRequestCounters(int pduType) {
    switch (pduType) {

      case pduGetRequestPdu:
        snmpInGetRequests++;
        break;
      case pduGetNextRequestPdu:
        snmpInGetNexts++;
        break;
      case pduSetRequestPdu:
        snmpInSetRequests++;
        break;
      default:
        break;
    }
    snmpInPkts++;
  }

  void updateErrorCounters(int errorStatus) {
    switch (errorStatus) {

      case snmpRspNoError:
        snmpOutGetResponses++;
        break;
      case snmpRspGenErr:
        snmpOutGenErrs++;
        break;
      case snmpRspBadValue:
        snmpOutBadValues++;
        break;
      case snmpRspNoSuchName:
        snmpOutNoSuchNames++;
        break;
      case snmpRspTooBig:
        snmpOutTooBigs++;
        break;
      default:
        break;
    }
    snmpOutPkts++;
  }

  void updateVarCounters(int pduType, int n) {
    switch (pduType) {

      case pduGetRequestPdu:
      case pduGetNextRequestPdu:
      case pduGetBulkRequestPdu:
        snmpInTotalReqVars += n;
        break;
      case pduSetRequestPdu:
        snmpInTotalSetVars += n;
        break;
    }
  }

  void incSnmpInASNParseErrs(int n) {
    snmpInASNParseErrs += n;
  }

  void incSnmpInBadVersions(int n) {
    snmpInBadVersions += n;
  }

  void incSnmpInBadCommunityUses(int n) {
    snmpInBadCommunityUses += n;
  }

  void incSnmpInBadCommunityNames(int n) {
    snmpInBadCommunityNames += n;
  }

  void incSnmpSilentDrops(int n) {
    snmpSilentDrops += n;
  }
  // PRIVATE METHODS
  //----------------

  /**
   * Returns the time (in hundreths of second) elapsed since the SNMP
   * protocol adaptor startup.
   */
  long getSysUpTime() {
    return (System.currentTimeMillis() - startUpTime) / 10;
  }

  /**
   * Control the way the SnmpAdaptorServer service is deserialized.
   */
  private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException {

    // Call the default deserialization of the object.
    //
    stream.defaultReadObject();

    // Call the specific initialization for the SnmpAdaptorServer service.
    // This is for transient structures to be initialized to specific
    // default values.
    //
    mibs = new Vector<>();
  }

  /**
   * Common initializations.
   */
  private void init(InetAddressAcl acl, int p, InetAddress a) {

    root = new SnmpMibTree();

    // The default Agent is initialized with a SnmpErrorHandlerAgent agent.
    root.setDefaultAgent(new SnmpErrorHandlerAgent());

    // For the trap time, use the time the agent started ...
    //
    startUpTime = java.lang.System.currentTimeMillis();
    maxActiveClientCount = 10;

    // Create the default message factory
    pduFactory = new SnmpPduFactoryBER();

    port = p;
    ipacl = acl;
    address = a;

    if ((ipacl == null) && (useAcl == true)) {
      throw new IllegalArgumentException("ACL object cannot be null");
    }

    threadService = new ThreadService(threadNumber);
  }

  SnmpMibAgent getAgentMib(SnmpOid oid) {
    return root.getAgentMib(oid);
  }

  @Override
  protected Thread createMainThread() {
    final Thread t = super.createMainThread();
    t.setDaemon(true);
    return t;
  }

}
